//
//  OMTAlbumManager.m
//  OMTImagePicker
//
//  Created by yizhaorong on 2017/11/15.
//

#import "OMTAlbumManager.h"
#import <AVFoundation/AVFoundation.h>
#import "YYImage.h"
#import <MobileCoreServices/UTCoreTypes.h>

#define OMT_SCREEN_SCALE ([UIScreen mainScreen].scale)

CGFloat const OMTAlbumManagerPadding = 3;
// 相册内容变化通知
NSString *const OMTAlbumManagerPhotoAssetsChangedNotificaton = @"OMTAlbumManagerPhotoAssetsChangedNotificaton";

static CGFloat const kDefaultItemWidth = 600;

static dispatch_queue_t omt_album_manager_processing_queue() {
    static dispatch_queue_t omt_album_manager_processing_queue;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        omt_album_manager_processing_queue = dispatch_queue_create("omt.album.manager.processing", DISPATCH_QUEUE_SERIAL);
    });
    
    return omt_album_manager_processing_queue;
}

@interface OMTAlbumManager() <PHPhotoLibraryChangeObserver>
// item宽高
@property (nonatomic, assign) CGSize itemSize;

@end

@implementation OMTAlbumManager

#pragma mark - Lifecycle
+ (instancetype)defaultManager {
    OMTAlbumManager *instance = [self new];
    instance.selectedPhotos = [NSMutableArray array];
    instance.builder = [OMTImagePickerBuilder defaultBuilder];;
    return instance;
}

- (void)dealloc {
    if ([OMTAlbumManager authorized]) {
        [[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self];
    }
}

- (instancetype)init {
    if (self = [super init]) {
        if ([OMTAlbumManager authorized]) {
            [[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self];
        }
    }
    return self;
}

+ (OMTAuthorizationStatus)authorizationStatus {
    return (OMTAuthorizationStatus)[PHPhotoLibrary authorizationStatus];
}

+ (BOOL)authorized {
    return [self authorizationStatus] == OMTAuthorizationStatusAuthorized;
}

+ (void)requestAuthorizationWithCompletion:(void (^)(OMTAuthorizationStatus status))completion {
    [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
        if (completion) {
            completion((OMTAuthorizationStatus)status);
        }
    }];
}

#pragma mark - Private
- (OMTAlbum *)modelWithResult:(id)result name:(NSString *)name{
    OMTAlbum *album = [[OMTAlbum alloc] init];
    album.albumManager = self;
    album.result = result;
    album.name = name;
    return album;
}

- (OMTAsset *)assetWithResult:(id)result {
    if (self.builder.hideWhenCanNotSelect && ![self isPhotoSelectableWithAsset:result]) return nil;
    
    OMTAssetMediaType type = OMTAssetMediaTypePhoto;
    NSString *duration = @"";
    PHAsset *asset = (PHAsset *)result;
    switch (asset.mediaType) {
        case PHAssetMediaTypeVideo: {
            type = OMTAssetMediaTypeVideo;
            duration = [self formatDuration:asset.duration];
        } break;
            
        case PHAssetMediaTypeAudio: {
            type = OMTAssetMediaTypeAudio;
        } break;
            
        case PHAssetMediaTypeImage: {
            type = OMTAssetMediaTypePhoto;
            if (@available(iOS 9.1, *)) {
                if (asset.mediaSubtypes == PHAssetMediaSubtypePhotoLive) {
                    type = OMTAssetMediaTypeLivePhoto;
                }
            }
        } break;
            
        default:
            break;
    }
    OMTAsset *albumAsset = [OMTAsset asset:result mediaType:type duration:duration];
    albumAsset.albumManager = self;
    return albumAsset;
}

- (NSString *)formatDuration:(NSTimeInterval)duration {
    NSString *newTime;
    if (lrint(duration) < 60) {
        newTime = [NSString stringWithFormat:@"00:%02ld", lrint(duration)];
    } else {
        NSInteger min = duration / 60;
        NSInteger sec = duration - (min * 60);
        if (sec < 10) {
            newTime = [NSString stringWithFormat:@"%zd:0%zd",min,sec];
        } else {
            newTime = [NSString stringWithFormat:@"%zd:%zd",min,sec];
        }
    }
    return newTime;
}


/**
 修正图片方向

 @param aImage 修正的图片
 @return 修正后的结果
 */
- (UIImage *)fixOrientation:(UIImage *)aImage {
    if (!self.builder.showVedio) return aImage;
    
    // No-op if the orientation is already correct
    if (aImage.imageOrientation == UIImageOrientationUp)
        return aImage;
    
    // We need to calculate the proper transformation to make the image upright.
    // We do it in 2 steps: Rotate if Left/Right/Down, and then flip if Mirrored.
    CGAffineTransform transform = CGAffineTransformIdentity;
    
    switch (aImage.imageOrientation) {
        case UIImageOrientationDown:
        case UIImageOrientationDownMirrored:
            transform = CGAffineTransformTranslate(transform, aImage.size.width, aImage.size.height);
            transform = CGAffineTransformRotate(transform, M_PI);
            break;
            
        case UIImageOrientationLeft:
        case UIImageOrientationLeftMirrored:
            transform = CGAffineTransformTranslate(transform, aImage.size.width, 0);
            transform = CGAffineTransformRotate(transform, M_PI_2);
            break;
            
        case UIImageOrientationRight:
        case UIImageOrientationRightMirrored:
            transform = CGAffineTransformTranslate(transform, 0, aImage.size.height);
            transform = CGAffineTransformRotate(transform, -M_PI_2);
            break;
        default:
            break;
    }
    
    switch (aImage.imageOrientation) {
        case UIImageOrientationUpMirrored:
        case UIImageOrientationDownMirrored:
            transform = CGAffineTransformTranslate(transform, aImage.size.width, 0);
            transform = CGAffineTransformScale(transform, -1, 1);
            break;
            
        case UIImageOrientationLeftMirrored:
        case UIImageOrientationRightMirrored:
            transform = CGAffineTransformTranslate(transform, aImage.size.height, 0);
            transform = CGAffineTransformScale(transform, -1, 1);
            break;
        default:
            break;
    }
    
    // Now we draw the underlying CGImage into a new context, applying the transform
    // calculated above.
    CGContextRef ctx = CGBitmapContextCreate(NULL, aImage.size.width, aImage.size.height,
                                             CGImageGetBitsPerComponent(aImage.CGImage), 0,
                                             CGImageGetColorSpace(aImage.CGImage),
                                             CGImageGetBitmapInfo(aImage.CGImage));
    CGContextConcatCTM(ctx, transform);
    switch (aImage.imageOrientation) {
        case UIImageOrientationLeft:
        case UIImageOrientationLeftMirrored:
        case UIImageOrientationRight:
        case UIImageOrientationRightMirrored:
            // Grr...
            CGContextDrawImage(ctx, CGRectMake(0,0,aImage.size.height,aImage.size.width), aImage.CGImage);
            break;
            
        default:
            CGContextDrawImage(ctx, CGRectMake(0,0,aImage.size.width,aImage.size.height), aImage.CGImage);
            break;
    }
    
    // And now we just create a new UIImage from the drawing context
    CGImageRef cgimg = CGBitmapContextCreateImage(ctx);
    UIImage *img = [UIImage imageWithCGImage:cgimg];
    CGContextRelease(ctx);
    CGImageRelease(cgimg);
    return img;
}


/**
 添加相册

 @param albums 相册
 @param type 类型
 @param subtype 子类型
 @param options 选项
 */
- (void)appendAlbum:(NSMutableArray *)albums
           WithType:(PHAssetCollectionType)type
            subtype:(PHAssetCollectionSubtype)subtype
            options:(PHFetchOptions *)options {
    PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:type subtype:subtype options:options];
    for (PHAssetCollection *collection in smartAlbums) {
        PHFetchResult *fetchResult = [PHAsset fetchAssetsInAssetCollection:collection options:options];
        if (fetchResult.count < 1) continue;
        OMTAlbum *album = [self modelWithResult:fetchResult name:collection.localizedTitle];
        album.type = type;
        album.subtype = subtype;
        album.options = options;
        [albums addObject:album];
    }
}

#pragma mark - Public


/**
 获取相册列表

 @param completion 回调
 */
- (void)allAlbumsWithCompletion:(void (^)(NSArray<OMTAlbum *> *, BOOL finished))completion {
    NSMutableArray *albums = [NSMutableArray array];
    PHFetchOptions *option = [PHFetchOptions new];
    option.predicate = [NSPredicate predicateWithFormat:@"mediaType == %ld", PHAssetMediaTypeImage];
    if (self.builder.showVedio && self.builder.showImage) {
        option.predicate = [NSPredicate predicateWithFormat:@"mediaType == %ld || mediaType == %ld", PHAssetMediaTypeImage, PHAssetMediaTypeVideo];
    } else if (!self.builder.showImage && self.builder.showVedio) {
        option.predicate = [NSPredicate predicateWithFormat:@"mediaType == %ld", PHAssetMediaTypeVideo];
    }
    NSSortDescriptor *createDateSortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:YES];
    option.sortDescriptors = @[createDateSortDescriptor];
    // 相机胶卷
    [self appendAlbum:albums WithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeSmartAlbumUserLibrary options:nil];
    // 自拍
    if (@available(iOS 9.0, *)) {
        [self appendAlbum:albums WithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeSmartAlbumSelfPortraits options:nil];
    }
    // 全景
    [self appendAlbum:albums WithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeSmartAlbumPanoramas options:nil];
    if (self.builder.showVedio) {
        // 视频
        [self appendAlbum:albums WithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeSmartAlbumVideos options:nil];
    }
    // 自定义相册
    [self appendAlbum:albums WithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeSmartAlbumGeneric options:nil];
    
    if (completion) completion(albums, YES);
}


/**
 获取默认图片

 @param asset 资源
 @param completion 成功回调
 */
- (PHImageRequestID)defaultPhotoWithAsset:(OMTAsset *)asset completion:(void (^)(UIImage *image, NSData *imageData, NSDictionary *info, BOOL isDegraded, OMTAsset *asset, BOOL finished))completion {
    CGFloat scale = OMT_SCREEN_SCALE + 1;
    CGFloat targetWidth = MIN(kDefaultItemWidth, self.itemSize.width * scale);
    return [self photoWithAsset:asset targetWitdh:targetWidth completion:completion];
}


/**
 获取图片

 @param asset 资源
 @param targetWitdh 目标宽度
 @param completion 成功回调
 */
- (PHImageRequestID)photoWithAsset:(OMTAsset *)asset targetWitdh:(CGFloat)targetWitdh completion:(void (^)(UIImage *image, NSData *imageData, NSDictionary *info, BOOL isDegraded, OMTAsset *asset, BOOL finished))completion {
    PHAsset *actualAsset = asset.asset;
    CGFloat scale = targetWitdh / actualAsset.pixelWidth;
    CGSize imageSize = CGSizeMake((int)targetWitdh, (int)(actualAsset.pixelHeight * scale));
    
    PHImageRequestOptions *option = [[PHImageRequestOptions alloc] init];
    option.resizeMode = PHImageRequestOptionsResizeModeFast;
    __weak typeof(self) weakSelf = self;
    return [[PHImageManager defaultManager] requestImageForAsset:actualAsset targetSize:imageSize contentMode:PHImageContentModeAspectFit options:option resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
        typeof(weakSelf) strongSelf = weakSelf;
        if (!strongSelf) return;
        
        BOOL downloadFinined = (![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey]);
        if (downloadFinined && result) {
            result = [strongSelf fixOrientation:result];
            BOOL isDegraded = [[info objectForKey:PHImageResultIsDegradedKey] boolValue];
            
            if (completion) {
                omt_album_dispatch_async_on_main_queue(^{
                    completion(result, nil, info, isDegraded, asset, YES);
                });
            }
            return;
        } else {
            if (completion) {
                omt_album_dispatch_async_on_main_queue(^{
                    if (completion) completion(nil, nil, nil, NO, asset, NO);
                });
            }
        }
        // Download image from iCloud / 从iCloud下载图片
        if ([info objectForKey:PHImageResultIsInCloudKey] && !result) {
            PHImageRequestOptions *option = [[PHImageRequestOptions alloc]init];
            option.networkAccessAllowed = YES;
            __weak typeof(strongSelf) weakSelf = strongSelf;
            [[PHImageManager defaultManager] requestImageDataForAsset:actualAsset options:option resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) {
                typeof(weakSelf) strongSelf = weakSelf;
                if (!strongSelf) return;
                
                dispatch_async(omt_album_manager_processing_queue(), ^{
                    UIImage *resultImage = [YYImage imageWithData:imageData];
                    if (resultImage) {
                        resultImage = [strongSelf fixOrientation:resultImage];
                        BOOL isDegraded = [[info objectForKey:PHImageResultIsDegradedKey] boolValue];
                        if (completion) {
                            omt_album_dispatch_async_on_main_queue(^{
                                completion(resultImage, imageData, info, isDegraded, asset, YES);
                            });
                        }
                    } else {
                        if (completion) {
                            omt_album_dispatch_async_on_main_queue(^{
                                if (completion) completion(nil, nil, nil, NO, asset, NO);
                            });
                        }
                    }
                });
            }];
        }
    }];
}

/**
 获取原图
 
 @param asset 资源
 @param completion 成功回调
 */
- (void)originalPhotoWithAsset:(OMTAsset *)asset completion:(void (^)(UIImage *image, NSData * imageData, NSDictionary *info, OMTAsset *asset, BOOL finished))completion {
    id actualAsset = asset.asset;
    if ([actualAsset isKindOfClass:[PHAsset class]]) {
        PHImageRequestOptions *option = [[PHImageRequestOptions alloc]init];
        option.networkAccessAllowed = YES;
        __weak typeof(self) weakSelf = self;
        
        [[PHImageManager defaultManager] requestImageDataForAsset:actualAsset options:option resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) {
            typeof(weakSelf) strongSelf = weakSelf;
            if (!strongSelf) return;
            
            BOOL downloadFinined = (![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey]);
            if (downloadFinined && imageData) {
                if (completion) {
                    UIImage *image = [YYImage imageWithData:imageData];
                    if (![dataUTI isEqualToString:(__bridge NSString *)kUTTypeGIF]) {
                        image = [strongSelf fixOrientation:image];
                    }
                    
                    omt_album_dispatch_async_on_main_queue(^{
                        if (completion) completion(image, imageData, info, asset, YES);
                    });
                }
            } else {
                if (completion) {
                    omt_album_dispatch_async_on_main_queue(^{
                        if (completion) completion(nil, nil, info, asset, NO);
                    });
                }
            }
        }];
    }
}

- (void)photoDataWithAsset:(OMTAsset *)asset completion:(void (^)(NSData * imageData, OMTAsset *asset, BOOL finished))completion {
    PHAsset *actualAsset = asset.asset;
    PHImageRequestOptions *option = [[PHImageRequestOptions alloc]init];
    option.networkAccessAllowed = YES;
    __weak typeof(self) weakSelf = self;
    
    [[PHImageManager defaultManager] requestImageDataForAsset:actualAsset options:option resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) {
        typeof(weakSelf) strongSelf = weakSelf;
        if (!strongSelf) return;
        
        BOOL downloadFinined = (![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey]);
        if (downloadFinined && imageData) {
            if (completion) {
                UIImage *image = [YYImage imageWithData:imageData];
                if (![dataUTI isEqualToString:(__bridge NSString *)kUTTypeGIF]) {
                    image = [strongSelf fixOrientation:image];
                }
                
                omt_album_dispatch_async_on_main_queue(^{
                    if (completion) completion(imageData, asset, YES);
                });
            }
        } else {
            if (completion) {
                omt_album_dispatch_async_on_main_queue(^{
                    if (completion) completion(nil, asset, NO);
                });
            }
        }
    }];
}

/**
 获取相册封面

 @param album 相册
 @param completion 封面
 @return 请求ID
 */
- (PHImageRequestID)coverWithAlbum:(OMTAlbum *)album completion:(void (^)(UIImage *, OMTAlbum *album, BOOL finished))completion {
    return [self defaultPhotoWithAsset:[album.assets firstObject] completion:^(UIImage *photo, NSData *imageData, NSDictionary *info, BOOL isDegraded, OMTAsset *asset, BOOL finished) {
        if (completion) completion(photo, album, finished);
    }];
}

- (void)refreshAlbum:(OMTAlbum *)album completion:(void (^)(OMTAlbum *album, BOOL finished))completion {
    PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:album.type subtype:album.subtype options:album.options];
    for (PHAssetCollection *collection in smartAlbums) {
        PHFetchResult *fetchResult = [PHAsset fetchAssetsInAssetCollection:collection options:album.options];
        if (fetchResult.count < 1 || ![collection.localizedTitle isEqualToString:album.name]) continue;
        album.result = fetchResult;
    }
    if (completion) {
        completion(album, YES);
    }
}


/**
 获取照片数组

 @param result 相册
 @param completion 回调
 */
- (void)assetsFromFetchResult:(id)result completion:(void (^)(NSArray<OMTAsset *> *, BOOL finished))completion {
    
    if ([result isKindOfClass:[PHFetchResult class]]) {
        __block NSMutableArray *assets = [NSMutableArray array];

        dispatch_barrier_async(omt_album_manager_processing_queue(), ^{
            PHFetchResult *fetchResult = (PHFetchResult *)result;
            __weak typeof(self) weakSelf = self;
            [fetchResult enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                typeof(weakSelf) strongSelf = weakSelf;
                if (!strongSelf) return;
                
                OMTAsset *assst = [strongSelf assetWithResult:obj];
                if (!assst) return;
                if (assst.mediaType == OMTAssetMediaTypeAudio) return;
                if (!strongSelf.builder.showVedio && assst.mediaType == OMTAssetMediaTypeVideo) return;
                if (!strongSelf.builder.showImage && assst.mediaType != OMTAssetMediaTypeVideo) return;
                [assets addObject:assst];
            }];
            // 按创建时间排序
            NSSortDescriptor *creationDateDesc = [NSSortDescriptor sortDescriptorWithKey:@"asset.creationDate" ascending:NO];
            // 按顺序添加排序描述器
            NSArray *descriptors = @[creationDateDesc];
            [assets sortUsingDescriptors:descriptors];
            
            if (completion) {
                omt_album_dispatch_async_on_main_queue(^{
                    completion(assets, YES);
                });
            }
        });
    }
}

- (void)cancelRequest:(PHImageRequestID)requestId {
    [[PHImageManager defaultManager] cancelImageRequest:requestId];
}

/// 检查照片大小或视频长度是否满足要求
- (BOOL)isPhotoSelectableWithAsset:(PHAsset *)asset {
    
    //视频才进行时长判断
    if (asset.mediaType == OMTAssetMediaTypeVideo) {
        NSInteger duration = ceil(asset.duration);
        if (duration > self.builder.maxVideoDuration) {
            return NO;
        }
    }
    
    //图片进行尺寸判断
    if (asset.mediaType == OMTAssetMediaTypePhoto) {
        CGSize photoSize = CGSizeMake(asset.pixelWidth, asset.pixelHeight);
        if (self.builder.minPhotoWidthSelectable > photoSize.width || self.builder.minPhotoHeightSelectable > photoSize.height) {
            return NO;
        }
    }
   
    return YES;
}

#pragma mark - Protocol conformance

#pragma mark - PHPhotoLibraryChangeObserver
- (void)photoLibraryDidChange:(PHChange *)changeInstance {
    [[NSNotificationCenter defaultCenter] postNotificationName:OMTAlbumManagerPhotoAssetsChangedNotificaton object:nil];
}

#pragma mark - Custom Accessors
//自定义 setter getter
- (CGSize)itemSize {
    CGFloat margin = OMTAlbumManagerPadding;
    CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
    CGFloat itemWH = (screenWidth - (self.builder.columnCount - 1) * margin) / self.builder.columnCount;
    _itemSize = CGSizeMake(itemWH, itemWH);
    return _itemSize;
}

@end
